import json
import os
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QStyledItemDelegate, QGridLayout, QPushButton, QFileDialog, QComboBox, QFrame, QGroupBox, QAbstractItemView, QTableWidgetItem, QTableWidget, QSpacerItem, QSizePolicy
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject, QEvent
from PyQt5.QtGui import QPainter, QPen, QColor, QDoubleValidator
from pydantic import BaseModel, Field, field_validator
from functools import partial
import gui.controls.envs as envs
from ..basic_grid_component import BasicGridComponent
from ..dlg_model_params import DlgModelParams
from ..c_params_table import CParamsTable
from ..dlg_params import DlgParams
from ..utils import Utils
from ..custom_widget import LineEditFloat, LineEditStr, LineEditInt, SelectBox, MultRadioBtnGroup


from enum import Enum

class FloatValidatorDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super(FloatValidatorDelegate, self).__init__(parent)
        self.validator = QDoubleValidator(self)

    def createEditor(self, parent, option, index):
        editor = super(FloatValidatorDelegate, self).createEditor(parent, option, index)
        editor.setValidator(self.validator)
        return editor

class CustomTable(CParamsTable):
    
    def __init__(self, rows=0, columns=2, parent=None):
        super(CustomTable, self).__init__(rows=rows, columns=columns, parent=parent)
        self.setEditTriggers(QAbstractItemView.DoubleClicked)
        self.verticalHeader().setVisible(True)
        self.setMinimumHeight(120)

class StrEnumWithAlias(str, Enum):
    def __new__(cls, value, alias):
        obj = str.__new__(cls, value)
        obj._value_ = value
        obj.alias = alias
        return obj
    
class MethodTypeEnum(StrEnumWithAlias):
    t1 = "几何参数优化", "几何参数优化"
    t2 = "复合材料铺层优化", "复合材料铺层优化"
    
class AlgorithmTypeEnum(StrEnumWithAlias):
    t1 = "梯度类算法SQP", "梯度类算法SQP"
    t2 = "启发式算法GA", "启发式算法GA"
    t3 = "CMA-ES", "CMA-ES"
    
class GoalTypeEnum(StrEnumWithAlias):
    t1 = "最大主应变最小化", "最大主应变最小化"

class ConstraintsTypeEnum(StrEnumWithAlias):
    t1 = "质量", "质量"
    t2 = "位移", "位移"
   

# 定义 Pydantic 模型
class OptimizationData(BaseModel):
    method: MethodTypeEnum = Field(
        description="优化方法",
        alias="method",
        required=True,
    )
    algorithm: AlgorithmTypeEnum = Field(
        description="优化算法",
        alias="algorithm",
        required=True,
    )
    goal: GoalTypeEnum = Field(
        description="优化目标",
        alias="goal",
        required=True,
    )
    constraints: ConstraintsTypeEnum = Field(
        description="约束条件",
        alias="constraints",
        required=True,
        extra={"type":"multRadio","hide":True}
    )
    wallThicknessT: float = Field(
        description="蜂窝芯子壁厚t",
        alias="wallThicknessT",
        required=True,
        extra={}
    )
    sideLengthL: float = Field(
        description="蜂窝芯子边长l",
        alias="sideLengthL",
        required=True,
        extra={"hide":True}
    )
    # 
    lapJointNumber: int = Field(
        description="搭接数量",
        alias="lapJointNumber",
        required=True,
        extra={"hide":True}
    )
    strengthenNumber: int = Field(
        description="加强层层数",
        alias="strengthenNumber",
        required=True,
        extra={"hide":True}
    )
   
    
    
class OptimizationComponent(BasicGridComponent):
    def __init__(self, parent=None):
        super().__init__(parent)
        
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        # self.gridLayout.setSpacing(20)
        # self.gridLayout.setVerticalSpacing(20)
        
    def get_model(self):
        return OptimizationData
       
    def setDefault(self):
        self.gridWidgets['method'].setValue("几何参数优化")
        self.gridWidgets['algorithm'].setValue("梯度类算法SQP")
    
    def updateUi(self,data):
        for k, v in data.items():
            if isinstance(self.gridWidgets[k], QTableWidget):
                self.loadTableData(self.gridWidgets[k],v, k)
            elif isinstance(self.gridWidgets[k], QComboBox):
                self.gridWidgets[k].setCurrentText(v)
            else:
                self.gridWidgets[k].setValue(v)
                
    def loadTableData(self, table, data, flag):
        for index, item in enumerate(data):
            if flag == "lapJointTable":
                table.addRow([item["宽度"], item["宽度"], item["初始值"], item["初始值"]])
            elif flag == "strengthenTable":
                table.addRow([item["厚度"], item["宽度"], item["初始值"], item["初始值"]])
        table.set_columns_editable()
        
        
    def updateUI(self):
        fileds = self.model.model_fields
        flagRow = 0
        for row, (field_name, field_info) in enumerate(fileds.items()):
            
            # multRadio
    #         class MultRadioBtnGroup(QWidget):
    # def __init__(self, parent=None, options=[], onChanged=None, isCol=False):
            
            buttonList = []
            disable = False
            hide = False
            type = None
            if field_info.json_schema_extra.get('extra'):
                if field_info.json_schema_extra.get('extra').get('hide'): 
                    hide = field_info.json_schema_extra.get('extra').get('hide')
                if field_info.json_schema_extra.get('extra').get('hide'): 
                    buttonList = field_info.json_schema_extra.get('extra').get('button')
                if field_info.json_schema_extra.get('extra').get('hide'): 
                    disable = field_info.json_schema_extra.get('extra').get('disable')
                if field_info.json_schema_extra.get('extra').get('type'): 
                    type = field_info.json_schema_extra.get('extra').get('type')
            if hide:
                flagRow = flagRow + 1
                continue
            row = row - flagRow
            label = QLabel(field_info.description)
            self.gridLayout.addWidget(label, row, 0)
            if issubclass(field_info.annotation, Enum):
                if type == "multRadio":
                    self.gridWidgets[field_name] = MultRadioBtnGroup(options=[{"value": item.alias} for item in list(field_info.annotation)]
                                                          )
                else:
                    self.gridWidgets[field_name] = SelectBox(options=[{"title":item.alias, "value":item.alias} for item in list(field_info.annotation)])
                    self.gridWidgets[field_name].setMinimumWidth(100)
                    # self.gridWidgets[field_name].setPlaceholderText(f"请选择{field_info.description}")
                    # self.gridWidgets[field_name].addItems(
                    #     [item.alias for item in list(field_info.annotation)]
                    # )
            else:
                field_type = field_info.annotation
                if field_type is float :
                    widget = LineEditFloat()
                elif field_type is int :
                    widget = LineEditInt()
                else:
                    widget = LineEditStr()
                if not disable:
                    widget.setPlaceholderText(f"请输入{field_info.description}")
                self.gridWidgets[field_name] = widget
            if disable:
                self.gridWidgets[field_name].setEnabled(False)
            self.gridLayout.addWidget(self.gridWidgets[field_name], row, 1)
            # if row != 0:
            #     spacer = QSpacerItem(20, 100, QSizePolicy.Minimum, QSizePolicy.Expanding)
            #     self.gridLayout.addItem(spacer, 2*row-1, 0, 1)  # 在第1行添加一个占位符，占据第0列和第1列
            for i, btnItem in enumerate(buttonList):
                self.gridWidgets[btnItem.get("name")] = QPushButton(btnItem.get("title"))
                self.gridLayout.addWidget(self.gridWidgets[btnItem.get("name")], row, 2+i)
        self.setLayout(self.gridLayout)

class DlgOptimization(DlgModelParams):
    optimization_save_signal = pyqtSignal(object)
    def __init__(self, parent=None):
        super(DlgOptimization, self).__init__(parent)
        
        self.setWindowTitle('优化方法')
        self.pBtnOK.setFocusPolicy(Qt.NoFocus)
        self.pBtnCancel.setFocusPolicy(Qt.NoFocus)
        self.setMinimumWidth(500)
        # self.resize(800, 500)
        layout = QGridLayout()
        # leftLayout = QVBoxLayout()
        # rightLayout = QVBoxLayout()
        self.component = OptimizationComponent() 
        topPlyLayers = self.createLayersTable("搭接数量", "lapJoint")
        topRepairPlyLayers = self.createLayersTable("加强层层数", "strengthen")
        topRepairPlyLayers2 = self.createLayersTable("加强层层数2", "strengthen2")
        layout.addLayout(topPlyLayers, 0, 0)
        layout.addLayout(topRepairPlyLayers2, 0, 1)
        layout.addLayout(topRepairPlyLayers, 1, 0)
        layout.addWidget(self.component, 1, 1)
        
        constraintsLayout = QHBoxLayout()
        constraintsLabel = QLabel("约束条件")
        self.component.gridWidgets['constraints'] = MultRadioBtnGroup(options=[{"value": "质量"},{"value": "位移"}])
        constraintsLayout.addWidget(constraintsLabel)
        constraintsLayout.addWidget(self.component.gridWidgets['constraints'])
        sideLengthLLayout = QHBoxLayout()
        sideLengthLLabel = QLabel("蜂窝芯子边长l")
        self.component.gridWidgets['sideLengthL'] = LineEditFloat()
        self.component.gridWidgets['sideLengthL'].setPlaceholderText("请输入蜂窝芯子边长l")
        sideLengthLLayout.addWidget(sideLengthLLabel)
        sideLengthLLayout.addWidget(self.component.gridWidgets['sideLengthL'])
        
        layout.addLayout(constraintsLayout, 2, 0)
        layout.addLayout(sideLengthLLayout, 2, 1)
        
        
        # leftLayout.addLayout(topPlyLayers)
        # leftLayout.addLayout(topRepairPlyLayers2)
        # rightLayout.addLayout(topRepairPlyLayers)
        # rightLayout.addWidget(self.component)
        
        # componentLayout = QHBoxLayout()
        
        # self.component = OptimizationComponent()
        # vLayout = QVBoxLayout()
        # vLayout.addWidget(self.component)
        # vLayout.addStretch(1)
        
        # componentLayout.addLayout(vLayout)
                
        # topPlyLayers = self.createLayersTable("搭接数量", "lapJoint")
        # topRepairPlyLayers = self.createLayersTable("加强层层数", "strengthen")
        # topRepairPlyLayers2 = self.createLayersTable("加强层层数2", "strengthen2")

        
        # vLayout = QVBoxLayout()
        # vLayout.addLayout(topPlyLayers)
        # vLayout.addLayout(topRepairPlyLayers)
        # vLayout.addLayout(topRepairPlyLayers2)

        
        # componentLayout.addItem(QSpacerItem(10, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
        # componentLayout.addLayout(vLayout)
        

        # layout.addLayout(componentLayout)
        
        self.vbox.insertLayout(0, layout)
        # self.vbox.insertWidget(0, self.component)
        
        self.component.setDefault()
        
        
        jsonPath = "data/ipimas/uis/Optimization.json"
        if not os.path.exists(jsonPath):
            data = Utils.read_json("data/ipimas/uis/Optimization.json")
        else:
            data = Utils.read_json(jsonPath)
        self.updateUi(data)
        
        self.component.gridWidgets['lapJointTable'].itemChanged.connect(partial(self.on_item_changed, flag='lapJoint'))
        self.component.gridWidgets['strengthenTable'].itemChanged.connect(partial(self.on_item_changed, flag='strengthen'))
        self.component.gridWidgets['strengthen2Table'].itemChanged.connect(partial(self.on_item_changed, flag='strengthen2'))
        
    def createLayersTable(self, title, name):
        numLayout = QHBoxLayout()
        label = QLabel(title)
        label.setFixedWidth(160)
        edit = LineEditInt()
        edit.setPlaceholderText(f"请输入{title}")
        edit.editingFinished.connect(lambda:self.on_text_changed(flag=name))
        self.component.gridWidgets[f'{name}Number'] = edit
        numLayout.addWidget(label)
        numLayout.addWidget(edit)
        numLayout.addStretch(1)
        layersLayout = QVBoxLayout()
        if name == "lapJoint":
            table = CustomTable(rows=0, columns=4)
            table.setItemDelegateForColumn(0, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(1, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(2, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(3, FloatValidatorDelegate(table))
            table.setHorizontalHeaderLabels(["宽度", "下限", "初始值", "上限"])
        elif name == "strengthen":
            table = CustomTable(rows=0, columns=4)
            table.setItemDelegateForColumn(0, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(1, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(2, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(3, FloatValidatorDelegate(table))
            table.setHorizontalHeaderLabels(["厚度", "下限", "初始值", "上限"])
        elif name == "strengthen2":
            table = CustomTable(rows=0, columns=4)
            table.setItemDelegateForColumn(0, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(1, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(2, FloatValidatorDelegate(table))
            table.setItemDelegateForColumn(3, FloatValidatorDelegate(table))
            table.setHorizontalHeaderLabels(["厚度", "下限", "初始值", "上限"])

        self.component.gridWidgets[f'{name}Table'] = table
        layersLayout.addLayout(numLayout)
        layersLayout.addWidget(table)

        return layersLayout
    
    def on_item_changed(self, item, flag):
        row = item.row()
        column = item.column()
        if column == 0:
            return
        value = toFloat(item.text())
        if value is not None:
            if flag == "lapJoint":
                title = "搭接"+str(row+1)
            else:
                title = "加强层"+str(row+1)
            table = self.component.gridWidgets[f'{flag}Table']
            if column == 1:
                initValue = toFloat(table.item(row, 1).text())
                upperValue = toFloat(table.item(row, 3).text())
                if initValue is not None and value > initValue:
                    item.setText("")
                    return envs.MBox.warning(f"{title}行，下限不可大于初始值") 
                if upperValue is not None and value > upperValue:
                    item.setText("")
                    return envs.MBox.warning(f"{title}行，下限不可大于上限")
            elif column == 2:
                lowerValue = toFloat(table.item(row, 1).text())
                upperValue = toFloat(table.item(row, 3).text())
                if lowerValue is not None and lowerValue > value:
                    item.setText("")
                    return envs.MBox.warning(f"{title}行，初始值不可小于下限") 
                if upperValue is not None and value > upperValue:
                    item.setText("")
                    return envs.MBox.warning(f"{title}行，初始值不可大于上限")
            elif column == 3:
                lowerValue = toFloat(table.item(row, 1).text())
                initValue = toFloat(table.item(row, 2).text()) 
                if lowerValue is not None and value < lowerValue:
                    item.setText("")
                    return envs.MBox.warning(f"{title}行，上限不可小于下限")
                if initValue is not None and value < initValue:
                    item.setText("")
                    return envs.MBox.warning(f"{title}行，上限不可小于初始值")

            
    
    def on_text_changed(self, flag):
        num = self.component.gridWidgets[f'{flag}Number'].text()
        self.update_table_rows(self.component.gridWidgets[f'{flag}Table'], num)

    def update_table_rows(self, table, num):
        try:
            num_rows = int(num)
            table.setRowCount(num_rows)
            table.set_columns_editable()
        except ValueError:
            table.setRowCount(0)

    def getTableDict(self, table, valueList, descriptionList, type):
        for i in range(table.rowCount()):
            rowDict = {}
            if type == 'lapJoint':
                typeList = ["宽度", "下限", "初始值", "上限"]
            elif type == 'strengthen':
                typeList = ["厚度", "下限", "初始值", "上限"]
            elif type == 'strengthen2':
                typeList = ["厚度", "下限", "初始值", "上限"]
            for j in range(table.columnCount()):
                item = table.item(i, j)
                rowDict[typeList[j]] = toFloat(item.text())
            valueList.append(rowDict)
            descriptionList.append(rowDict)
        return valueList
    
    @pyqtSlot()
    def on_pBtnOK_clicked(self):
        
        for flag in ["lapJoint", "strengthen"]:
            self.on_text_changed(flag=flag)
        
        valueRes = self.component.getValues()
        descriptionRes = self.component.getValuesUseDescription()
        
        valueRes['strengthenTable'] = []
        valueRes['lapJointTable'] = []

        descriptionRes['搭接'] = []
        descriptionRes['加强层'] = []

        
        self.getTableDict(self.component.gridWidgets['lapJointTable'], valueRes['lapJointTable'], descriptionRes['搭接'], 'lapJoint')
        self.getTableDict(self.component.gridWidgets['strengthenTable'], valueRes['strengthenTable'],descriptionRes['加强层'], 'strengthen')

            
        if not self.check_empty_values(descriptionRes['搭接']):
            return envs.MBox.warning("请补搭接数据")
        if not self.check_empty_values(descriptionRes['加强层']):
            return envs.MBox.warning("请补全加强层数据")
     
        
        file = "data/ipimas/uis/Optimization.json"
        Utils.write_json(file, valueRes)
        self.close()
        self.optimization_save_signal.emit(descriptionRes)
    
    def check_empty_values(self, data_list):
        for item in data_list:
            for key, value in item.items():
                if value == "" or value is None:
                    return False
        return True
    
    def is_table_filled(self, table):
        # 遍历表格中的每个单元格
        for row in range(self.table.rowCount()):
            for column in range(self.table.columnCount()):
                item = self.table.item(row, column)
                # 检查单元格是否为空
                if not item or item.text().strip() == "":
                    return False  # 如果找到空单元格，返回False
        return True  # 所有单元格都已填写
        
    def closeEvent(self, event):
        DlgOptimization._shown = False  # 使用类名访问类属性
        DlgOptimization._instance = None  # 清除实例引用
        event.accept()
        
def toFloat(value):
    try:
        return float(value)
    except ValueError:
        return None